﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Windows.Forms;

namespace UIControls.Utilities
{
    /// <summary>
    /// An iterator which returns a FileInfo object, one at a time, of the file
    /// </summary>
    class GSJpegger : IEnumerable<FileInfo>
    {
        /// <summary>
        /// Get the value of the post script file we are rendering
        /// </summary>
        public FileInfo PSFile { get; private set; }

        /// <summary>
        /// Get the DPI we are doing the rendering at
        /// </summary>
        public int DPI { get; private set; }

        /// <summary>
        /// Returns the location where the various jpegs's etc. have been written.
        /// </summary>
        public DirectoryInfo TempDirectory { get; private set; }

        /// <summary>
        /// The non-space sanitized name of the output file. gs can't deal with output filenames that contain spaces.
        /// </summary>
        private string epsOutputName;

        /// <summary>
        /// The non-space sanitized name of the talk.
        /// </summary>
        private string talkName;

        /// <summary>
        /// Create a GS converter that will run the psFile given. Nothing is actually
        /// started until the enumerator is called for its first result.
        /// </summary>
        /// <param name="psFile"></param>
        public GSJpegger(FileInfo psFile, int DPI)
        {
            PSFile = psFile;
            this.DPI = DPI;

            ///
            /// Create the output directory (but don't create it).
            /// 

            talkName = PSFile.Name.Substring(0, PSFile.Name.Length - PSFile.Extension.Length);

            epsOutputName = talkName.Replace(' ', '-');
            TempDirectory = new DirectoryInfo(Path.GetTempPath() + "\\GSRendering\\" + epsOutputName);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public System.Collections.Generic.IEnumerator<FileInfo> GetEnumerator()
        {
            ///
            /// Make sure the temp directory exists before we do anything with it!
            /// 

            if (!TempDirectory.Exists)
            {
                TempDirectory.Create();
            }

            FileInfo logInfo = new FileInfo(TempDirectory.FullName + "\\" + talkName + "-log.txt");

            ///
            /// Determine the DPI that we want to be rendering at! Run it at twice the DPI of the screen.
            /// 

            int resolution = DPI * 2;

            ///
            /// Spec out the process we will start.
            /// 

            ProcessStartInfo sinfo = new ProcessStartInfo();
            sinfo.FileName = ExternalToolLocations.LocationGS;
            sinfo.Arguments = "-sDEVICE=jpeg "
                + "-dJPEGQ=100 "
                + "-r" + resolution.ToString() + "x" + resolution.ToString() + " "
                + "-sOutputFile=\"" + epsOutputName + "-%03d.jpg\" "
                + "-dBATCH -dNOPAUSE ";

            if (IsEPSFile())
            {
                sinfo.Arguments += "-dEPSCrop ";
            }

            sinfo.Arguments += "\"" + PSFile.FullName + "\"";
            sinfo.CreateNoWindow = true;
            sinfo.RedirectStandardOutput = true;
            sinfo.UseShellExecute = false;
            sinfo.WorkingDirectory = TempDirectory.FullName;

            ///
            /// Now start the process. We switch the error mode
            /// so that when gs crashes it doesn't pop the dialog
            /// box. And because this is a process wide setting, we need
            /// to make sure two people don't try to start the process
            /// at once.
            /// 

            DateTime startime = DateTime.Now;
            Process gsproc = null;

            lock (this)
            {
                using (ChangeErrorMode newerr = new ChangeErrorMode(ChangeErrorMode.ErrorModes.FailCriticalErrors | ChangeErrorMode.ErrorModes.NoGpFaultErrorBox))
                {
                    gsproc = Process.Start(sinfo);
                    try
                    {
                        gsproc.PriorityClass = ProcessPriorityClass.Idle;
                    }
                    catch
                    {
                    }
                }
            }

            ///
            /// Start a thread that will record all the error output, and will
            /// quit when the thread has finished. This information is just for
            /// debugging when something goes badly wrong. Nothing from this is really
            /// used.
            /// 

            Thread ioWatchThread = new Thread(() => WatchForErrorIO(logInfo, sinfo, gsproc));
            ioWatchThread.Start();

            ///
            /// Scan the output directory for files as they are written. Of course, we only want the
            /// pages as they are finished.
            /// 

            int page_number = 1;
            bool addedImage = true;
            FileInfo lastSeenImage = null;
            while (!gsproc.WaitForExit(0) || addedImage)
            {
                addedImage = false;
                FileInfo fn = new FileInfo(TempDirectory.FullName + "\\" + epsOutputName + "-" + page_number.ToString("000") + ".jpg");

                if (fn.Exists)
                {
                    if (lastSeenImage == null)
                    {
                        lastSeenImage = fn;
                        page_number += 1;
                        addedImage = true;
                    }
                    else if (lastSeenImage.FullName != fn.FullName)
                    {
                        yield return lastSeenImage;
                        lastSeenImage = fn;
                        page_number += 1;
                        addedImage = true;
                    }
                }
            }

            if (lastSeenImage != null)
            {
                yield return lastSeenImage;
            }
        }

        /// <summary>
        /// Returns true if this is an EPS file
        /// </summary>
        /// <returns></returns>
        private bool IsEPSFile()
        {
            return PSFile.Extension == ".eps";
        }

        /// <summary>
        /// Return a enumerator
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            foreach (var f in this)
            {
                yield return f;
            }
        }

        /// <summary>
        /// Watch the output of the process and record it. When we are done, we are done (as they say).
        /// </summary>
        /// <param name="logInfo"></param>
        /// <param name="sinfo"></param>
        /// <param name="gsproc"></param>
        private static void WatchForErrorIO(FileInfo logInfo, ProcessStartInfo sinfo, Process gsproc)
        {
            ///
            /// Now monitor the i/o that comes back from there.
            /// 

            using (TextWriter wr = logInfo.CreateText())
            {
                wr.WriteLine("Converting PDF to jpeg");
                wr.WriteLine("ghost script path: " + sinfo.FileName);
                wr.WriteLine("Arguments: " + sinfo.Arguments);
                wr.WriteLine("Log:");

                string line = "hi";
                while ((line = gsproc.StandardOutput.ReadLine()) != null)
                {
                    line = line.Trim();
                    wr.WriteLine(line);
                }
                wr.Close();
            }

            ///
            /// Be good, wait for it to shut down - which should be seconds
            /// 

            gsproc.WaitForExit();
        }
    }
}
